/*
 * BasicTypes.h
 *
 *  Created on: 10.08.2011
 *      Author: Blinov Kirill
 *   Copyright: LGPL
 *
 */
#ifndef BASICTYPES_H_
#define BASICTYPES_H_

#include <stdint.h>
#include <stddef.h>
#include <stdio.h>

#ifdef WIN32

#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/wglew.h>
#include <GL/glext.h>

#elif defined(linux) || defined(__linux)
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#include <GL/glu.h>
#elif defined(__APPLE__)
#	include <OpenGL/gl.h>
#else
#	error "Unsupported platform"
#endif

#include <string>

#include <gli/gli.hpp>
#include <gli/gtx/gl_texture2d.hpp>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/half_float.hpp>
#include <glm/gtc/type_precision.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtx/quaternion.hpp>

// TODO Задефайнить типы, как говорил Филёк. Вот он этим и займётся))
//typedef int   __int32
//typedef short	__int16

#define BYTE   unsigned char
#define SHORT  short int
#define USHORT unsigned short int
#define UINT   unsigned int
#define UINT64 unsigned long long

//#define ENGINE_DEBUG

enum SHADER_TYPES
{
	SHADER_TYPE_SIMPLE_LIGHTING_NO_SHADOW = 0,
	SHADER_TYPE_FONG_LIGHTING_NO_SHADOW,
	SHADER_TYPE_FONG_LIGHTING_SHADOW_VOLUME,
	SHADER_TYPE_FONG_LIGHTING_SHADOW_VOLUME_BONES_ANIM,
	SHADER_TYPE_WATER,
	SHADER_TYPE_GUI,
	SHADER_TYPE_MAX
};

enum SHADER_INPUT_PARAMS
{
	SHADER_INPUT_MVP_MATRIX = 0,
	SHADER_INPUT_MODEL_MATRIX,
	SHADER_INPUT_MATRIX_ARRAY_32,
	SHADER_INPUT_LIGHT_DIRECTION,
	SHADER_INPUT_AMBLIENT_COLOR,
	SHADER_INPUT_CAMERA_DISTANCE,
	SHADER_INPUT_MAX
};

/*
 * Unit types
 */
enum UNIT_TYPES
{
	UNIT_TYPE_HEAVY_JET = 0,
	UNIT_TYPE_MEDIUM_JET ,
	UNIT_TYPE_LIGHT_JET,
	UNIT_TYPE_RACKET_,
	UNIT_TYPE_MILK,
	UNIT_TYPE_BULLET,
	UNIT_TYPE_BONUS,
	UNIT_TYPE_SCENARY,
	UNIT_TYPES_TOTAL_COUNT
};

struct SCREEN_DIMENSION
{
	SCREEN_DIMENSION(){};
	SCREEN_DIMENSION( UINT Height, UINT Width ):
		height(Height), width(Width), sdlFlags(0), multisampleBufferCount(0)
	{
	};
	SCREEN_DIMENSION( const SCREEN_DIMENSION& Dimension ) :
		height(Dimension.height), width(Dimension.width), sdlFlags(Dimension.sdlFlags),
		multisampleBufferCount(Dimension.multisampleBufferCount)
	{
	};
	UINT  height;
	UINT  width;
	UINT  sdlFlags;
	USHORT multisampleBufferCount;
};

/*
 * Structure for unit's physic parameters
 */
struct UNIT_PHYSIC_PARAMS
{
	UNIT_PHYSIC_PARAMS() : initialPosition( 0.0, 0.0, 0.0 ), maxSpeed(0), currentSpeed(0),
			               maxAxeleration(0), currentAxeleration(0), weight(0)
	{}
	bool Update( const UNIT_PHYSIC_PARAMS &PhysicParams )
	{
		initialPosition = PhysicParams.initialPosition;

		if ( ( PhysicParams.maxSpeed < 0 ) && ( abs(PhysicParams.maxSpeed) > maxSpeed ) )
		{
			return false;
		}
		else
		{
			maxSpeed += PhysicParams.maxSpeed;
		}

		if ( ( PhysicParams.currentSpeed < 0 ) && ( abs(PhysicParams.maxSpeed) > currentSpeed ) )
		{
			return false;
		}
		else
		{
			currentSpeed += PhysicParams.currentSpeed;
		}

		if ( ( PhysicParams.maxAxeleration < 0 ) && ( abs(PhysicParams.maxAxeleration) > maxAxeleration ) )
		{
			return false;
		}
		else
		{
			maxAxeleration += PhysicParams.maxAxeleration;
		}

		if ( ( PhysicParams.currentAxeleration < 0 ) && ( abs(PhysicParams.currentAxeleration) > currentAxeleration ) )
		{
			return false;
		}
		else
		{
			currentAxeleration += PhysicParams.currentAxeleration;
		}

		weight += PhysicParams.weight;
		return true;
	}

	glm::vec3 initialPosition;
	float     maxSpeed;
	float     currentSpeed;
	float     maxAxeleration;
	float     currentAxeleration;
	float     weight;
    //TODO Add limitations for rotation
};

/*
 * One vertex structure
 */
struct STANDART_VERTEX
{
	STANDART_VERTEX():
		position(0.0f,0.0f,0.0f),normal(0.0f,0.0f,0.0f),texture(0.0f,0.0f), bonesWeights(1.0f,0.0f,0.0f), bonesIndexesAndTextureNumber(1.0f,0.0f,0.0f,0.0f) {};
	STANDART_VERTEX( glm::vec3 Position, glm::vec3 Normal, glm::vec2 Texture, glm::vec3 Weights, glm::vec3 Indexes, float TextureNumber ) :
		position(Position), normal(Normal), texture(Texture), bonesWeights(Weights), bonesIndexesAndTextureNumber(Indexes, TextureNumber) {};
	glm::vec3 position;
	glm::vec3 normal;
	glm::vec2 texture;
	glm::vec3 bonesWeights;
	glm::vec4 bonesIndexesAndTextureNumber;
};

/*
 * Contains information necessary for render a 3D model
 */
struct RENDER_MODEL_INFO
{
	RENDER_MODEL_INFO() : primitiveType(GL_TRIANGLES) {};
	RENDER_MODEL_INFO( SHADER_TYPES ShaderType, GLuint VertexCount, std::vector<GLushort> Indexes ) :
	    shaderType(ShaderType), vertexCount(VertexCount), indexes(Indexes),
	    indexType(GL_UNSIGNED_SHORT), primitiveType(GL_TRIANGLES) {};
	//Type of shader programm the object to be rendered
	SHADER_TYPES            shaderType;
	GLuint                  vertexCount;
	std::vector<GLushort>   indexes;

	//Offset from the buffer first vertex ( equals sum of vertices in previous models )
	UINT                    offsetInVertexBuffer;

	//usually GL_UNSIGNED_SHORT
	GLenum                  indexType;

	//Type of primitives GL_TRIANGLES for example
	GLenum                  primitiveType;
};

/*
 * Contains array of vertices and texture numbers describing a 3D model
 */
struct PURE_COMPRESSED_MODEL
{
	PURE_COMPRESSED_MODEL( std::vector<STANDART_VERTEX> Vertices, std::vector<GLushort> Indexes, std::vector<GLuint> Textures ):
		vertices(Vertices), indexes(Indexes), textures(Textures) {};
	std::vector<STANDART_VERTEX> vertices;
	std::vector<GLushort>        indexes;
	std::vector<GLuint>          textures; //FIXME Вероятно, что это не нужно
	//Reference to structure to save offset in vertex buffer
	RENDER_MODEL_INFO           *renderInfo;
};

class OBJECT_POSITION_DESCRIPTOR
{
private:
	glm::gtx::quaternion::quat  orientation;
	glm::vec3                   translation;
	glm::mat4                   position;
public:
	OBJECT_POSITION_DESCRIPTOR(): translation(1.0f), position(1.0f), orientation( 0.0f, glm::vec3( 1.0f, 0.0f, 0.0f ))
	{
	}
	virtual ~OBJECT_POSITION_DESCRIPTOR(){}

	void Move( const glm::vec3 Translation )
	{
		translation += Translation;
		position *= glm::translate( glm::mat4(1.0f), Translation );
	};

	void Rotate( const float Angle, const glm::vec3 RotationAxis )
	{
		glm::gtx::quaternion::quat tmpQuat = glm::gtx::quaternion::angleAxis( Angle, RotationAxis );
		orientation = orientation * tmpQuat;
		position = glm::translate( glm::mat4(1.0f), Translation ) * glm::gtx::quaternion::toMat4( orientation );
	}

	glm::mat4 GetWorldMatrix()const{ return position; };
};

class GAME_SUBOBJECT_INSTANCE
{
private:
	//Position relatively to the center of main object
	OBJECT_POSITION_DESCRIPTOR  positionDescriptor;
public:
	GAME_SUBOBJECT_INSTANCE(){};
	GAME_SUBOBJECT_INSTANCE( RENDER_MODEL_INFO* RenderInfo, glm::mat4 Position, glm::gtx::quaternion::quat Orientation,
			                 std::vector<GLuint> Textures ) :
		                     renderInfo( RenderInfo ), translation( Position ), orientation( Orientation ), textures( Textures ), enabled(true)
							 { position = translation * glm::gtx::quaternion::toMat4( orientation ); }
	virtual ~GAME_SUBOBJECT_INSTANCE(){}
	glm::mat4 GetWorldMatrix(){ return positionDescriptor.GetWorldMatrix(); }

	RENDER_MODEL_INFO  *renderInfo;
	//Array of texture numbers
	std::vector<GLuint>         textures;
	//Matrixes for bones
	std::vector<glm::mat4>      bonesMatrixes;

	bool                        enabled;
};

struct GUI_ELEMENT
{
	glm::vec2 position;
	glm::vec2 size;
	GLuint texture;
	std::string text;
	glm::vec2 positionText;
	glm::vec2 sizeText;
};

/*
 * Camera render options
 */
struct VISUAL_PARAMS
{

};

/*
 * Camera description
 */
const USHORT PARALLELEPIPED_PLANE_COUNT = 6;
struct GAME_CAMERA
{
	GAME_CAMERA() : lightDirection( 1.0f, 0.0f, 0.0f ), amblientColor( 0.0f, 0.0f, 0.0f )
	{ };
	float         			leftTopX;
	float         			leftTopY;
	float         			height;
	float         			width;
	VISUAL_PARAMS 			visualParams;
	glm::vec3     			lightDirection;
	glm::vec3     			amblientColor;
	glm::vec4     			sphere; //Culling sphere
	glm::vec4               frustum[PARALLELEPIPED_PLANE_COUNT]; //Culling frustum
	glm::mat4     			viewMatrix;
	glm::mat4     			projectionMatrix;
};

/*
 * All necessary information to render a frame
 */
struct FULL_FRAME_DATA
{
	std::vector<GAME_OBJECT*>  gameObjectsArray;
	std::vector<GAME_CAMERA*>  camerasArray;
	std::vector<GUI_ELEMENT*>  guiElementsArray;
	GLuint                     vertexArrayId;
	RENDER_MODEL_INFO*         guiDummy;
};

/*
 *
 *
 *
 */
const USHORT MOUSE_BUTTON_LEFT = 0;
const USHORT MOUSE_BUTTON_MIDDLE = 1;
const USHORT MOUSE_BUTTON_RIGHT = 2;

const USHORT MOUSE_KEY_COUNT = 3;
const USHORT MOUSE_KEY_DOWN = 0;
const USHORT MOUSE_KEY_UP = 1;

struct INPUT_MESSAGE
{
	INPUT_MESSAGE(): terminateApplication(false), mouseDelta( 0.0f, 0.0f ), mousePosition ( 0.0f, 0.0f )
			    {
					memset( mouseKeys, 0, sizeof(mouseKeys) );
					keys.reserve( 5 ); //reserve space for 5 keys
			    }

	//Should the application be terminated
	bool terminateApplication;

	//Delta of mouse position
	glm::vec2 mouseDelta;

	//TODO Delete when start using animated cusor.
	glm::vec2 mousePosition;

	//Mouse keys
    USHORT mouseKeys[MOUSE_KEY_COUNT];

	//Hight bit is a state bit ( 1 - Down, 0 - Up ), other bits are ASCII character or a special key code
	std::vector<USHORT> keys;
};

/*Unit event*/
struct UINT_EVENT
{
	UINT type;
};

#endif /* BASICTYPES_H_ */
